/* Copyright 2009 Requirements Management System 
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.mags.remas.logic;

import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang.StringUtils;
import org.mags.common.EngineContext;
import org.mags.dao.common.ESortOrder;
import org.mags.remas.model.entity.Entity;
import org.mags.remas.model.entity.EntityCategory;
import org.mags.remas.model.entity.EntityReference;
import org.mags.remas.model.entity.EntityRelationship;
import org.mags.remas.model.entity.EntityStructure;
import org.mags.remas.model.entity.EntityType;
import org.mags.remas.model.entity.EnumValue;
import org.mags.remas.model.entity.KeyValue;
import org.mags.remas.model.entity.ListValue;
import org.mags.remas.model.entity.provider.EnumValueProvider;
import org.mags.remas.model.entity.provider.KeyTypeProvider;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;

/**
 * 
 * @author Mario Gaitan
 *
 */
public class EntityLogic {
	private static EntityLogic instance = null;
	public static EntityLogic getInstance() {
		if(instance==null)
			instance = new EntityLogic();
		return instance;
	}
	private EntityLogic() {
	}
	public void loadKeys(EntityRelationship child) {
		loadKeys(new EntityRelationship[]{child});
	}
	public void loadKeys(Object[] children) {
		List<Object> entityIds = new ArrayList<Object>();
		List<EntityRelationship> objects = new ArrayList<EntityRelationship>();
		for(Object _i: children) {
			EntityRelationship i = (EntityRelationship)_i;
			entityIds.add(i.getEntity().getEntityId());
			objects.add(i);
			if(i.getLink()!=null) {
				objects.add(i.getLink());
				entityIds.add(i.getLink().getEntity().getEntityId());
			}
		}
		if(entityIds.size()>0) {			
			List<KeyValue> values = EngineContext.getDataContext().getEntityProviderFactory().getKeyValueProvider().getByEntityIds(entityIds.toArray());
			KeyTypeProvider keyTypeProvider = EngineContext.getDataContext().getEntityProviderFactory().getKeyTypeProvider();
			EnumValueProvider enumValueProvider = EngineContext.getDataContext().getEntityProviderFactory().getEnumValueProvider();
			List<ListValue> listValues = EngineContext.getDataContext().getEntityProviderFactory().getListValueProvider().getByIds(entityIds.toArray());
			
			long currentEntityId = 0;
			Map<String, Object> keys = null;
			Set<KeyValue> keyValues = null;
			Object value = null;
			for(KeyValue i: values) {
				value = null;
				if(keyTypeProvider.isENUM(i.getKeyDefinition().getKeyType())&&i.getEnumValue()!=null) 
					value = enumValueProvider.getById(i.getEnumValue().getEnumValueId()).getEvalue();
				else if(keyTypeProvider.isDATE(i.getKeyDefinition().getKeyType())&&i.getDateValue()!=null)
					value = i.getDateValue();
				else if(keyTypeProvider.isFLOAT(i.getKeyDefinition().getKeyType())&&i.getNumericValue()!=null)
					value = i.getNumericValue();
				else if(keyTypeProvider.isINTEGER(i.getKeyDefinition().getKeyType())&&i.getNumericValue()!=null)
					value = i.getNumericValue();
				else if(keyTypeProvider.isVARCHAR(i.getKeyDefinition().getKeyType())&&i.getVarcharValue()!=null)
					value = i.getVarcharValue();
				else if(keyTypeProvider.isLIST(i.getKeyDefinition().getKeyType())) {
					List<ListValue> _listValues = new ArrayList<ListValue>();
					for(ListValue v: listValues)
						if(v.getKeyDefinitionId()==i.getKeyDefinition().getKeyDefinitionId()
						 && v.getEntityId()==i.getEntityId()) 
							_listValues.add(v);
					if(_listValues.size()>0)
						value = _listValues;
				}
				if(currentEntityId!=i.getEntityId()) {
					if(keys!=null) {
						for(EntityRelationship c: objects)
							if(c.getEntity().getEntityId()==currentEntityId) {
								c.getEntity().setKeys(keys);
								c.getEntity().setKeyValues(keyValues);
								break;
							}
					}
					keys = new HashMap<String, Object>();
					keyValues = new HashSet<KeyValue>();
					currentEntityId = i.getEntityId();
				}
				if(value!=null 
					&& !(value instanceof String && StringUtils.isEmpty((String)value))) {
					keys.put(i.getKeyDefinition().getName(), value);
					keyValues.add(i);
				}
			}
			if(keys!=null&&keys.size()>0) {
				for(EntityRelationship c: objects)
					if(c.getEntity().getEntityId()==currentEntityId) {
						c.getEntity().setKeys(keys);
						c.getEntity().setKeyValues(keyValues);
						break;
					}
			}
		}
	}	
	public List<Integer> getEntityTypeIdByCategoryIdAndViewTypeId(short entityCategoryId, short viewTypeId) {
		List<Integer> entityTypeIds = new ArrayList<Integer>();
		for(EntityStructure i: EngineContext.getDataContext().getEntityProviderFactory().getEntityStructureProvider().getByCategoryId(entityCategoryId))
			if(i.getViewType().getViewTypeId()==viewTypeId)
				entityTypeIds.add(i.getEntityType().getEntityTypeId());
		return entityTypeIds;
	}
	public List<Integer> getEntityTypeIdByCategoryId(short entityCategoryId) {
		List<Integer> entityTypeIds = new ArrayList<Integer>();
		for(EntityStructure i: EngineContext.getDataContext().getEntityProviderFactory().getEntityStructureProvider().getByCategoryId(entityCategoryId))
			entityTypeIds.add(i.getEntityType().getEntityTypeId());
		return entityTypeIds;
	}
	public List<EntityStructure> getEntityTypeChildren(Object object) {
		List<EntityStructure> structure = null;
		if(object instanceof EntityStructure) {
			EntityStructure entityStructure = (EntityStructure)object;
			structure = EngineContext.getDataContext().getEntityProviderFactory().getEntityStructureProvider().getByParentId(entityStructure.getEntityType().getEntityTypeId(), entityStructure.getEntityType().getEntityCategory().getEntityCategoryId());
		} else if(object instanceof EntityType) {
			EntityType entityType = (EntityType)object;
			structure = EngineContext.getDataContext().getEntityProviderFactory().getEntityStructureProvider().getByParentId(entityType.getEntityTypeId(), entityType.getEntityCategory().getEntityCategoryId());
		}
		return structure;
	}	
	
	public List<Integer> getIdEntityTypeList(EntityCategory entityCategory) {
		List<Integer> idEntityTypeList = new ArrayList<Integer>();
		List<EntityType> entityTypeList = EngineContext.getDataContext().getEntityProviderFactory().getEntityTypeProvider().getByCategoryId(entityCategory.getEntityCategoryId());
		for(EntityType i: entityTypeList)
			idEntityTypeList.add(i.getEntityTypeId());
		return idEntityTypeList;
	}
	
	
	
	/**
	 * get child of EntityRelationship instance
	 * @param entityRelationship 
	 * children of this object will be obtained, read only parameter 
	 * @return List<EntityRelationship> 
	 * children list founded
	 */
	public List<EntityRelationship> getEntityChildren(EntityRelationship entityRelationship, List<Integer> entityTypeIds) {
		String[][] orderBy = new String[][]{{"position", ESortOrder.dsc.name()}};
		return EngineContext.getDataContext().getEntityProviderFactory().getEntityRelationshipProvider().getByParent(entityRelationship, orderBy, entityTypeIds);
	}

	/**
	 * update entityRelationship and its entity property
	 * @param entityRelationship
	 * relationship to update, read/write parameter, read/write entity property
	 */
	public void update(final EntityRelationship entityRelationship, final List<KeyValue> keyValues) {
		EngineContext.getDataContext().getTransactionTemplate().execute(
			new TransactionCallbackWithoutResult() {
				protected void doInTransactionWithoutResult(TransactionStatus status) {
					if(entityRelationship.getLink()!=null) {
						EntityRelationship link = entityRelationship.getLink();
						entityRelationship.setLinkEntityType(link.getEntity().getEntityType());
						entityRelationship.setLinkEntityTypeParent(link.getEntityParent().getEntityType());
						entityRelationship.setRelationshipLinkId(link.getEntityRelationshipId());
					} else {
						entityRelationship.setLinkEntityType(null);
						entityRelationship.setLinkEntityTypeParent(null);
						entityRelationship.setRelationshipLinkId(null);
					}
					EngineContext.getDataContext().getEntityProviderFactory().getEntityRelationshipProvider().update(entityRelationship);
					EngineContext.getDataContext().getEntityProviderFactory().getEntityProvider().update(entityRelationship.getEntity());
					if(keyValues!=null) {
						for(KeyValue i: keyValues) {
							i.setEntityId(entityRelationship.getEntity().getEntityId());
							EnumValue enumValue = i.getEnumValue();
							if(enumValue!=null&&enumValue.getEnumValueId()==0) 
									enumValue = null;
							i.setEnumValue(enumValue);
							EngineContext.getDataContext().getEntityProviderFactory().getKeyValueProvider().createOrUpdate(i);
						}				
					}
				}
			}
		);
	}

	/**
	 * it eliminates the entityRelationship and its descendant relationships
	 * @param entityRelationship
	 * relationship of which the children will be deleted
	 * relationship is deleted too, read only parameter
	 * @param parentRelationship
	 * to order the relationship brothers, read only parameter
	 */
	public void delete(final EntityRelationship entityRelationship, final EntityRelationship parentRelationship, final EntityCategory entityCategory) {
		EngineContext.getDataContext().getTransactionTemplate().execute(
			new TransactionCallbackWithoutResult() {
				protected void doInTransactionWithoutResult(TransactionStatus status) {
					EngineContext.getDataContext().getEntityProviderFactory().getEntityRelationshipProvider().deleteDescendants(entityRelationship);
					EngineContext.getDataContext().getEntityProviderFactory().getEntityProvider().deleteLonelyEntities(getIdEntityTypeList(entityCategory), EngineContext.getDataContext().getEntityProviderFactory().getEntityProvider().getROOT(entityCategory.getEntityCategoryId()));
					EngineContext.getDataContext().getEntityProviderFactory().getEntityRelationshipProvider().updatePositionDown(parentRelationship, entityRelationship.getPosition()+1, -1);
					/*TODO: lock elements where delete children*/
					if(!entityRelationship.getHierarchicalPath().equals("/"))
						EngineContext.getDataContext().getEntityProviderFactory().getEntityRelationshipProvider().updateVersionDescendants(entityRelationship);					
				}	
			}
		);
	}
	
	private void createRelationship(final EntityRelationship entityRelationship, final EntityRelationship parentEntityRelationship, final List<KeyValue> keyValues) {	
		if(entityRelationship.getEntity().getEntityId()==0) {
			EntityType entityType = EngineContext.getDataContext().getEntityProviderFactory().getEntityTypeProvider().getByIdForUpdate(entityRelationship.getEntity().getEntityType().getEntityTypeId());
			entityType.setChildrenCount(entityType.getChildrenCount()+1);	
			NumberFormat formatter = new DecimalFormat("0000");
			String code = formatter.format(entityType.getChildrenCount());
			entityRelationship.getEntity().setCode(entityType.getCode()+"-"+code);						
			EngineContext.getDataContext().getEntityProviderFactory().getEntityProvider().create(entityRelationship.getEntity());
			EngineContext.getDataContext().getEntityProviderFactory().getEntityTypeProvider().update(entityType);
		}
		entityRelationship.setHierarchicalPath(parentEntityRelationship.getHierarchicalPath()+entityRelationship.getEntity().getEntityId()+"/");
		entityRelationship.setEntityParent(parentEntityRelationship.getEntity());
		EngineContext.getDataContext().getEntityProviderFactory().getEntityRelationshipProvider().updatePositionUp(parentEntityRelationship, -1, -1);
		entityRelationship.setPosition(1);
		entityRelationship.setEntityType(entityRelationship.getEntity().getEntityType());
		entityRelationship.setEntityTypeParent(entityRelationship.getEntityParent().getEntityType());
		if(entityRelationship.getLink()!=null) {
			EntityRelationship link = (EntityRelationship)entityRelationship.getLink();
			entityRelationship.setLinkEntityType(link.getEntityType());
			entityRelationship.setLinkEntityTypeParent(link.getEntityTypeParent());
			entityRelationship.setRelationshipLinkId(link.getEntityRelationshipId());
		}  else {
			entityRelationship.setLinkEntityType(null);
			entityRelationship.setLinkEntityTypeParent(null);
			entityRelationship.setRelationshipLinkId(null);			
		}
		EngineContext.getDataContext().getEntityProviderFactory().getEntityRelationshipProvider().create(entityRelationship);
		if(keyValues!=null) {
			for(KeyValue i: keyValues) {
				i.setEntityId(entityRelationship.getEntity().getEntityId());
				EnumValue enumValue = i.getEnumValue();
				if(enumValue!=null&&enumValue.getEnumValueId()==0) 
						enumValue = null;
				i.setEnumValue(enumValue);				
				EngineContext.getDataContext().getEntityProviderFactory().getKeyValueProvider().createOrUpdate(i);
			}
		}
		/*TODO: lock parent entity relationship to avoid nobody add new child*/
		if(!parentEntityRelationship.getHierarchicalPath().equals("/"))
			EngineContext.getDataContext().getEntityProviderFactory().getEntityRelationshipProvider().update(parentEntityRelationship);
	}

	/**
	 * add a relationship, create entity property if not exists
	 * @param entityRelationship
	 * entityRelationship to add, read/write parameter, read/write entity property
	 * @param parentEntityRelationship
	 * parent of new relationship, read only parameter
	 */
	public void create(final EntityRelationship entityRelationship, final EntityRelationship parentEntityRelationship, final List<KeyValue> keyValues) {
		EngineContext.getDataContext().getTransactionTemplate().execute(
			new TransactionCallbackWithoutResult() {
				protected void doInTransactionWithoutResult(TransactionStatus status) {					
					createRelationship(entityRelationship, parentEntityRelationship, keyValues);
				}					
			}
		);
	}
	
	/**
	 * add relationship list, create each entity if not exists
	 * @param entityRelationshipList
	 * entityRelationship list to add, read/write each entityRelationship, read/write entity property
	 * @param parentEntityRelationship
	 * parent of new relationships, read only parameter
	 */
	public void create(final List<EntityRelationship> entityRelationshipList, final EntityRelationship parentEntityRelationship) {
		EngineContext.getDataContext().getTransactionTemplate().execute(
			new TransactionCallbackWithoutResult() {
				protected void doInTransactionWithoutResult(TransactionStatus status) {					
					for(EntityRelationship entityRelationship: entityRelationshipList) {
						createRelationship(entityRelationship, parentEntityRelationship, null);
					}			
				}					
			}
		);
	}

	public void referenceFrom(final List<EntityRelationship> entityRelationshipList, final EntityRelationship reference) {
		EngineContext.getDataContext().getTransactionTemplate().execute(
			new TransactionCallbackWithoutResult() {
				protected void doInTransactionWithoutResult(TransactionStatus status) {					
					for(EntityRelationship er: entityRelationshipList) {
						EntityReference entityReference = new EntityReference();
						entityReference.setEntity(reference.getEntity());
						entityReference.setReference(er.getEntity());
						entityReference.setReferenceStatus(EngineContext.getDataContext().getEntityProviderFactory().getReferenceStatusProvider().getActiveStatus());
						EngineContext.getDataContext().getEntityProviderFactory().getEntityReferenceProvider().create(entityReference);
					}			
				}					
			}
		);
	}

	public void referenceTo(final List<EntityRelationship> entityRelationshipList, final EntityRelationship reference) {
		EngineContext.getDataContext().getTransactionTemplate().execute(
			new TransactionCallbackWithoutResult() {
				protected void doInTransactionWithoutResult(TransactionStatus status) {					
					for(EntityRelationship er: entityRelationshipList) {
						EntityReference entityReference = new EntityReference();
						entityReference.setEntity(er.getEntity());
						entityReference.setReference(reference.getEntity());
						entityReference.setReferenceStatus(EngineContext.getDataContext().getEntityProviderFactory().getReferenceStatusProvider().getActiveStatus());
						EngineContext.getDataContext().getEntityProviderFactory().getEntityReferenceProvider().create(entityReference);
					}			
				}					
			}
		);
	}
	
	/**
	 * move entityRelationship from its position to relationshipToGetPosition position
	 * @param entityRelationship
	 * entityRelationship to move, read/write parameter
	 * @param relationshipToGetPosition
	 * entityRelationship reference to get its position, read only parameter
	 * @param parentEntityRelationship
	 * parent of relationship to move, read only parameter
	 * @param abovePosition
	 * move above of relationshipToGetPosition if true, below else
	 */
	public void movePositionUp(final EntityRelationship entityRelationship, final EntityRelationship relationshipToGetPosition, final EntityRelationship parentEntityRelationship, final Boolean abovePosition) {
		EngineContext.getDataContext().getTransactionTemplate().execute(
				new TransactionCallbackWithoutResult() {
					protected void doInTransactionWithoutResult(TransactionStatus status) {
						Integer position = entityRelationship.getPosition();
						entityRelationship.setPosition(0);
						EngineContext.getDataContext().getEntityProviderFactory().getEntityRelationshipProvider().update(entityRelationship);
						int endPosition=0;
						if(abovePosition)
							endPosition = relationshipToGetPosition.getPosition()+1;
						else
							endPosition = relationshipToGetPosition.getPosition();
						EngineContext.getDataContext().getEntityProviderFactory().getEntityRelationshipProvider().updatePositionDown(parentEntityRelationship, position, endPosition-1);
						if(abovePosition)
							entityRelationship.setPosition(relationshipToGetPosition.getPosition());
						else
							entityRelationship.setPosition(relationshipToGetPosition.getPosition()-1);
						EngineContext.getDataContext().getEntityProviderFactory().getEntityRelationshipProvider().update(entityRelationship);
					}
				}
		);
	}
	
	/**
	 * move entityRelationship from its position to relationshipToGetPosition position
	 * @param entityRelationship
	 * entityRelationship to move, read/write parameter
	 * @param relationshipToGetPosition
	 * relationship reference to get its position, read only parameter
	 * @param parentEntityRelationship
	 * parent of relationship to move, read only parameter
	 * @param belowPosition
	 * move below of relationshipToGetPosition if true, above else
	 */
	public void movePositionDown(final EntityRelationship entityRelationship, final EntityRelationship relationshipToGetPosition, final EntityRelationship parentEntityRelationship, final Boolean belowPosition) {
		EngineContext.getDataContext().getTransactionTemplate().execute(
				new TransactionCallbackWithoutResult() {
					protected void doInTransactionWithoutResult(TransactionStatus status) {
						Integer position = entityRelationship.getPosition();
						entityRelationship.setPosition(0);
						EngineContext.getDataContext().getEntityProviderFactory().getEntityRelationshipProvider().update(entityRelationship);
						int startPosition=0;
						if(belowPosition)
							startPosition = relationshipToGetPosition.getPosition()-1;
						else
							startPosition = relationshipToGetPosition.getPosition();
						EngineContext.getDataContext().getEntityProviderFactory().getEntityRelationshipProvider().updatePositionUp(parentEntityRelationship, startPosition+1, position);
						if(belowPosition)
							entityRelationship.setPosition(relationshipToGetPosition.getPosition());
						else
							entityRelationship.setPosition(relationshipToGetPosition.getPosition()+1);
						EngineContext.getDataContext().getEntityProviderFactory().getEntityRelationshipProvider().update(entityRelationship);						
					}
				}
		);		
	}	
	
	/**
	 * change parent to entityRelationship
	 * @param entityRelationship
	 * relationship to change parent, read/write parameter
	 * @param newParentEntityRelationship
	 * new parent to assign, read only parameter
	 * @param currentParentEntityRelationship
	 * current parent of entityRelationship, read only parameter
	 */
	public void changeParent(final EntityRelationship entityRelationship, final EntityRelationship newParentEntityRelationship, final EntityRelationship currentParentEntityRelationship, final EntityCategory entityCategory) {
		EngineContext.getDataContext().getTransactionTemplate().execute(
			new TransactionCallbackWithoutResult() {
				protected void doInTransactionWithoutResult(TransactionStatus status) {
					EntityRelationship _newParent = null;
					if(newParentEntityRelationship.getEntity().getEntityId()!=EngineContext.getDataContext().getEntityProviderFactory().getEntityProvider().getROOT(entityCategory.getEntityCategoryId()).getEntityId())
						_newParent = EngineContext.getDataContext().getEntityProviderFactory().getEntityRelationshipProvider().getByIdForUpdate(newParentEntityRelationship.getEntityRelationshipId());
					EngineContext.getDataContext().getEntityProviderFactory().getEntityRelationshipProvider().updateHierarchicalPath(entityRelationship, newParentEntityRelationship);
					Entity oldParent = entityRelationship.getEntityParent();
					int startPosition=entityRelationship.getPosition();
					entityRelationship.setEntityParent(newParentEntityRelationship.getEntity());
					entityRelationship.setEntityTypeParent(newParentEntityRelationship.getEntity().getEntityType());
					entityRelationship.setPosition(1);
					entityRelationship.setHierarchicalPath(newParentEntityRelationship.getHierarchicalPath()+entityRelationship.getHierarchicalPath().substring(entityRelationship.getHierarchicalPath().indexOf("/"+String.valueOf(entityRelationship.getEntity().getEntityId())+"/")+1));
					EngineContext.getDataContext().getEntityProviderFactory().getEntityRelationshipProvider().updatePositionUp(newParentEntityRelationship, -1, -1);
					EngineContext.getDataContext().getEntityProviderFactory().getEntityRelationshipProvider().update(entityRelationship);
					EngineContext.getDataContext().getEntityProviderFactory().getEntityRelationshipProvider().updatePositionDown(currentParentEntityRelationship, startPosition+1, -1);
					if(_newParent!=null&&(newParentEntityRelationship.getEntityParent().getEntityId()!=oldParent.getEntityId()||newParentEntityRelationship.getPosition()<=startPosition)) 
						EngineContext.getDataContext().getEntityProviderFactory().getEntityRelationshipProvider().update(_newParent);
				}
			}
		);
	}	
}
